import * as THREE from 'three';
import {
  AmbientLight,
  BoxBufferGeometry,
  BufferGeometry,
  DirectionalLight,
  Geometry,
  Material,
  Matrix3,
  Mesh,
  MeshLambertMaterial,
  Object3D,
  Raycaster,
  SphereBufferGeometry,
  Texture,
  Vector2,
  Vector3,
} from 'three';
import BaseThreeApp from '../BaseThreeApp';
import DebugInfo from '../DebugInfo';

class _11_1RayPick extends BaseThreeApp {
  public scene: THREE.Scene;
  public camera: THREE.Camera;
  public cameraTaget: Mesh;
  public renderer: THREE.WebGLRenderer;
  public geos: (Geometry | BufferGeometry)[] = [];
  public mats: Material[] = [];
  public texs: Texture[] = [];
  public raycaster: Raycaster;
  public mousePositon: Vector2 = new Vector2();
  public intersectObject: Object3D = null;

  start() {
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(
      60,
      this.clientWidth / this.clientHeight,
      0.1,
      3000
    );
    this.camera.position.z = 150;
    this.cameraTaget = new Mesh();

    this.renderer = new THREE.WebGLRenderer({
      canvas: this.element,
    });
    this.renderer.setDrawingBufferSize(this.clientWidth, this.clientHeight, 1);

    const dirLight = new DirectionalLight(0xffffff, 0.6);
    dirLight.position.set(1000, 1000, 1000);
    dirLight.target.position.set(0, 0, 0);
    this.scene.add(dirLight);

    const dirLight2 = new DirectionalLight(0xffffff, 0.3);
    dirLight2.position.set(-1000, -1000, -1000);
    this.scene.add(dirLight2);

    const ambient = new AmbientLight(0xffffff, 0.2);
    this.scene.add(ambient);

    const cubeCount = 2000;
    const geometrys: BufferGeometry[] = [
      new BoxBufferGeometry(10, 10, 10),
      new SphereBufferGeometry(10),
    ];
    this.geos.push(...geometrys);
    const geoCount = geometrys.length;
    const width = 800;
    for (let i = 0; i < cubeCount; i++) {
      const geo = geometrys[Math.floor(Math.random() * geoCount)];
      const mat = new MeshLambertMaterial({ color: Math.random() * 0xffffff });
      this.mats.push(mat);
      const obj = new Mesh(geo, mat);

      obj.position.setX((Math.random() - 0.5) * width);
      obj.position.setY((Math.random() - 0.5) * width);
      obj.position.setZ((Math.random() - 0.5) * width);

      obj.scale.setX(Math.random() + 0.5);
      obj.scale.setY(Math.random() + 0.5);
      obj.scale.setZ(Math.random() + 0.5);

      this.scene.add(obj);
    }

    this.raycaster = new Raycaster();

    this.isRunning = true;
    const animate = () => {
      if (!this.isRunning || this.element === null) {
        return;
      }
      // obj2.setRotationFromMatrix(this.camera.matrixWorld);

      DebugInfo.Instance.begin();
      this.raycaster.setFromCamera(this.mousePositon, this.camera);
      const intersects = this.raycaster.intersectObjects(this.scene.children);
      if (this.intersectObject) {
        const obj = this.intersectObject as Mesh;
        const material = obj.material as MeshLambertMaterial;
        material.emissive.set(0x000000);
        this.intersectObject = null;
      }
      if (intersects.length > 0) {
        this.intersectObject = intersects[0].object;
        const obj = this.intersectObject as Mesh;
        const material = obj.material as MeshLambertMaterial;
        material.emissive.set(0xff0000);
      }

      this.renderer.render(this.scene, this.camera);
      DebugInfo.Instance.end();
      requestAnimationFrame(animate);
    };
    animate();
  }

  onresize(e: UIEvent) {
    super.onresize(e);
    console.log('onresive');
    if (this.renderer) {
      this.renderer.setDrawingBufferSize(
        this.clientWidth,
        this.clientHeight,
        1
      );
      if (this.camera && this.camera instanceof THREE.PerspectiveCamera) {
        this.camera.aspect = this.clientWidth / this.clientHeight;
        this.camera.updateProjectionMatrix();
      }
    }
  }

  public onkeydown(e: KeyboardEvent): any | void {
    if (e.key === 'r' || ' ') {
      this.cameraTaget.position.set(0, 0, 0);
      this.camera.position.set(0, 0, 50);
      this.camera.lookAt(this.cameraTaget.position);
    }
  }

  public onmousewheel(e: WheelEvent): any | void {
    if (!this.camera) {
      return;
    }
    const taget = this.cameraTaget.position;
    const axisZ = new Vector3().subVectors(this.camera.position, taget);
    let len = taget.distanceTo(this.camera.position);
    const dir = e.deltaY > 0 ? 1.2 : 0.8;
    len *= dir;
    if (len < 1 || len > 2000) {
      return;
    }
    axisZ.normalize().multiplyScalar(len);
    this.camera.position.copy(taget.clone().add(axisZ));
  }

  public onmousemove(e: MouseEvent): any | void {
    this.mousePositon.x = (e.offsetX / this.clientWidth) * 2 - 1;
    this.mousePositon.y = -(e.offsetY / this.clientHeight) * 2 + 1;

    if (!this.camera || !(e.buttons & 0x1 || e.buttons & 0x2)) {
      return;
    }
    const scale = 0.5;
    const matrix = new Matrix3().setFromMatrix4(this.camera.matrixWorld);
    const axisX = new Vector3(1, 0, 0).applyMatrix3(matrix).normalize();
    const axisY = new Vector3(0, 1, 0).applyMatrix3(matrix).normalize();
    axisX.multiplyScalar(e.movementX * scale);
    axisY.multiplyScalar(e.movementY * scale);
    const cameraPos = this.camera.position;
    cameraPos.add(axisX);
    cameraPos.add(axisY);
    if (e.buttons & 0x1) {
    } else if (e.buttons & 0x2) {
      this.cameraTaget.position.add(axisX);
      this.cameraTaget.position.add(axisY);
    }
    this.camera.lookAt(this.cameraTaget.position);
  }

  public oncontextmenu(e: MouseEvent): any | void {
    e.cancelBubble = true;
    e.returnValue = false;
    return false;
  }

  destory() {
    this.geos.forEach((v) => v.dispose());
    this.geos = [];
    this.mats.forEach((v) => v.dispose());
    this.mats = [];
    this.texs.forEach((v) => v.dispose());
    this.texs = [];
    super.destory();
  }
}

export default new _11_1RayPick();
